import json
import logging
import re
import subprocess

from PyQt5 import QtGui
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QTextCursor, QFont, QIcon, QStandardItemModel, QStandardItem
from PyQt5.QtWidgets import QGridLayout, QPushButton, QPlainTextEdit, QLabel, QLineEdit, QInputDialog, \
    QMessageBox, QTableWidget, QTableWidgetItem, QHeaderView, QDialog, QSplitter, QWidget, \
    QVBoxLayout, QAbstractItemView, QMenu, QAction, QFrame, QHBoxLayout, QScrollArea, QTreeView, \
    QApplication, QStyle

from comp_cab_setup_modules.config import hostname_expression, version, ip_expression, hosts_file_path, suffix, \
    config_path
from comp_cab_setup_modules.design import TableCheckBox, get_name_with_next_number, GetCommandForSSHRun, \
    StyledItemDelegate, users_admins_generator
from comp_cab_setup_modules.hosts import Hosts
from comp_cab_setup_modules.system import user, CompKabSolutionWindow, run_command, poweroff, \
    run_command_by_root
from comp_cab_setup_modules.workers import SSHRootSetup, VeyonInstall, \
    SSHCommandInThreads, PingTest, SSHDelete, СreateStudent, Autologin, VeyonSetup, DeleteStudent, GetIPByMac, \
    RestrictDesktopChanges, RestrictNetwork, ShowHostsNames, VeyonRemoveKeys, \
    RestartVeyonOnHosts, EnableRemoteLogin


class SettingsWindow(CompKabSolutionWindow):
    def __init__(self):
        super().__init__()

        upper_widget = QWidget()

        self.hosts = Hosts()
        grid = QGridLayout()
        upper_widget.setLayout(grid)
        self.setWindowTitle(f'Настройка компьютерного кабинета, версия {version}')
        self.setMinimumWidth(1000)

        self.textfield = QPlainTextEdit()
        self.textfield.cursor = QTextCursor()
        self.textfield.setReadOnly(True)

        self.right_panel_splitter = QSplitter()
        self.right_panel_splitter.setOrientation(Qt.Vertical)
        self.right_panel_splitter.addWidget(upper_widget)
        self.right_panel_splitter.addWidget(self.textfield)
        height1 = run_command(f'py-ini-config get {config_path}/ccs.conf Main settings_tab_right_widget_height1')
        height2 = run_command(f'py-ini-config get {config_path}/ccs.conf Main settings_tab_right_widget_height2')
        if not height1:
            height1 = 0
        if not height2:
            height2 = 0
        last_nonzero_height2 = run_command(f'py-ini-config get {config_path}/ccs.conf Main '
                                           f'settings_tab_right_widget_nonzero_height2')
        if height1 and height2:
            self.right_panel_splitter.setSizes([int(height1), int(height2)])
        if not last_nonzero_height2:
            nonzero_height = int(height2) if int(height2) else self.right_panel_splitter.height() // 2
            run_command(f'py-ini-config set {config_path}/ccs.conf Main settings_tab_right_widget_nonzero_height2 '
                        f'{nonzero_height}')
        self.right_panel_splitter.splitterMoved.connect(self.set_nonzero_splitter_size)

        settings_window_hboxlayout = QHBoxLayout()
        self.left_panel = QWidget()
        left_panel_layout = QVBoxLayout()
        self.left_panel.setLayout(left_panel_layout)
        left_panel_scroll_area = QScrollArea()
        left_panel_scroll_area.setWidget(self.left_panel)
        left_panel_scroll_area.setWidgetResizable(True)
        left_panel_scroll_area.setMinimumWidth(300)

        right_panel = QWidget()
        vboxlayout = QVBoxLayout()
        vboxlayout.addWidget(self.right_panel_splitter)
        right_panel.setLayout(vboxlayout)

        settings_window_splitter = QSplitter()
        settings_window_splitter.setOrientation(Qt.Horizontal)
        settings_window_splitter.addWidget(left_panel_scroll_area)
        settings_window_splitter.addWidget(right_panel)
        settings_window_hboxlayout.addWidget(settings_window_splitter)
        self.setLayout(settings_window_hboxlayout)

        hostslabel = QLabel('Список устройств:')
        hostslabel.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
        grid.addWidget(hostslabel, 0, 0)

        button = QPushButton('')
        button.clicked.connect(self.add_row)
        button.setToolTip('Добавить строку в таблицу')
        button.setIcon(QIcon('/usr/share/icons/elementary/actions/48/list-add.svg'))
        grid.addWidget(button, 0, 1)

        button = QPushButton('')
        button.clicked.connect(self.delete_row)
        button.setToolTip('Удалить текущую строку в таблице')
        button.setIcon(QIcon('/usr/share/icons/elementary/actions/48/list-remove.svg'))
        grid.addWidget(button, 0, 2)

        button = QPushButton('Очистить...')
        button.clicked.connect(self.delete_all)
        button.setToolTip('Очистить таблицу')
        grid.addWidget(button, 0, 3)

        right_panel_separator = QFrame()
        right_panel_separator.setFrameShape(QFrame.VLine)
        right_panel_separator.setStyleSheet('color:#b3b3b3')
        grid.addWidget(right_panel_separator, 0, 4)

        generate_admins_btn = QPushButton('Генерация админов')
        generate_admins_btn.clicked.connect(self.generate_admins)
        generate_admins_btn.setToolTip('Позволяет задать одинаковые имена пользователей и пароли '
                                       'для админов на компьютерах учеников')
        grid.addWidget(generate_admins_btn, 0, 5)

        generate_users_btn = QPushButton('Генерация пользователей')
        generate_users_btn.clicked.connect(self.generate_users)
        generate_users_btn.setToolTip('Позволяет задать одинаковые имена пользователей и пароли '
                                      'для пользователей на компьютерах учеников')
        grid.addWidget(generate_users_btn, 0, 6)

        right_panel_separator = QFrame()
        right_panel_separator.setFrameShape(QFrame.VLine)
        right_panel_separator.setStyleSheet('color:#b3b3b3')
        grid.addWidget(right_panel_separator, 0, 7)

        import_btn = QPushButton('Импорт')
        import_btn.clicked.connect(self.show_import_menu)
        import_btn.setToolTip('Позволяет импортировать адреса устройств из текстового файла')
        grid.addWidget(import_btn, 0, 8)

        export_btn = QPushButton('Экспорт')
        export_btn.clicked.connect(self.export_json)
        export_btn.setToolTip('Позволяет экспортировать все адреса с настройками')
        grid.addWidget(export_btn, 0, 9)

        self.hosts_table = QTableWidget()
        self.hosts_table.setColumnCount(10)

        self.update_data(key='load')

        self.hosts_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
        grid.addWidget(self.hosts_table, 1, 0, 12, 10)
        self.hosts_table.itemChanged.connect(self.change_data)

        if user == 'root':
            logging.info("Попытка запустить от рута")
            messageBox = QMessageBox.warning(
                self,
                "Неверный пользователь!",
                f"Данное приложение не следует запускать от имени суперпользователя",
                QMessageBox.Ok,
            )
            if messageBox == QMessageBox.Ok:
                self.close()
        elif user == 'student':
            logging.info("Попытка запустить от студента")
            messageBox = QMessageBox.warning(
                self,
                "Неверный пользователь!",
                f"Данное приложение не следует запускать от имени ученика",
                QMessageBox.Ok,
            )
            if messageBox == QMessageBox.Ok:
                self.close()
        else:
            self.treeview = QTreeView()

            network_groupbox = QWidget()
            network_groupbox_layout = QVBoxLayout()
            network_groupbox.setLayout(network_groupbox_layout)

            self.button_ping = QPushButton('Проверить ping')
            self.button_ping.clicked.connect(self.test_ping)
            network_groupbox_layout.addWidget(self.button_ping)

            self.button_ssh = QPushButton('Настроить доступ по ssh')
            self.button_ssh.clicked.connect(self.setup_ssh)
            network_groupbox_layout.addWidget(self.button_ssh)

            self.button_removessh = QPushButton('Удалить ключи ssh')
            self.button_removessh.clicked.connect(self.delete_ssh)
            network_groupbox_layout.addWidget(self.button_removessh)

            self.button_getipbymac = QPushButton('Получить IP по MAC')
            self.button_getipbymac.clicked.connect(self.get_ip_by_mac)
            network_groupbox_layout.addWidget(self.button_getipbymac)

            users_groupbox = QWidget()
            users_groupbox_layout = QVBoxLayout()
            users_groupbox.setLayout(users_groupbox_layout)

            self.button_create_student = QPushButton('Создать учётные записи учеников')
            self.button_create_student.clicked.connect(self.create_student)
            users_groupbox_layout.addWidget(self.button_create_student)

            self.button_remove_student = QPushButton('Удалить учётные записи учеников')
            self.button_remove_student.clicked.connect(self.delete_student)
            users_groupbox_layout.addWidget(self.button_remove_student)

            self.autologin_enable = QPushButton('Включить автологин учеников')
            self.autologin_enable.clicked.connect(self.autologin_enable_func)
            users_groupbox_layout.addWidget(self.autologin_enable)

            self.autologin_disable = QPushButton('Выключить автологин учеников')
            self.autologin_disable.clicked.connect(self.autologin_disable_func)
            users_groupbox_layout.addWidget(self.autologin_disable)

            veyon_groupbox = QWidget()
            veyon_groupbox_layout = QVBoxLayout()
            veyon_groupbox.setLayout(veyon_groupbox_layout)

            self.button_install_veyon = QPushButton('Установить Veyon')
            self.button_install_veyon.clicked.connect(self.install_veyon)
            veyon_groupbox_layout.addWidget(self.button_install_veyon)

            self.button_setup_veyon = QPushButton('Настроить Veyon')
            self.button_setup_veyon.clicked.connect(self.setup_veyon)
            veyon_groupbox_layout.addWidget(self.button_setup_veyon)

            self.button_remove_veyon_keys_on_server = QPushButton('Удалить ключи с сервера')
            self.button_remove_veyon_keys_on_server.clicked.connect(self.remove_veyon_keys_on_server)
            veyon_groupbox_layout.addWidget(self.button_remove_veyon_keys_on_server)

            self.button_remove_veyon_keys_on_hosts = QPushButton('Удалить ключи с хостов')
            self.button_remove_veyon_keys_on_hosts.clicked.connect(self.remove_veyon_keys_on_hosts)
            veyon_groupbox_layout.addWidget(self.button_remove_veyon_keys_on_hosts)

            self.button_restart_veyon_on_server = QPushButton('Рестарт службы на сервере')
            self.button_restart_veyon_on_server.clicked.connect(self.restart_veyon_on_server)
            veyon_groupbox_layout.addWidget(self.button_restart_veyon_on_server)

            self.button_restart_veyon_on_hosts = QPushButton('Рестарт службы на хостах')
            self.button_restart_veyon_on_hosts.clicked.connect(self.restart_veyon_on_hosts)
            veyon_groupbox_layout.addWidget(self.button_restart_veyon_on_hosts)

            # self.button_add_server = QPushButton('Создать ещё один сервер')
            # self.button_add_server.clicked.connect(self.add_another_server)
            # veyon_groupbox_layout.addWidget(self.button_add_server)
            # self.button_add_server.setDisabled(True)
            # self.button_add_server.setToolTip('В разработке')

            commands_groupbox = QWidget()
            commands_groupbox_layout = QVBoxLayout()
            commands_groupbox.setLayout(commands_groupbox_layout)

            # self.command_exec = QPushButton('Выполнить команду от админа')
            # self.command_exec.clicked.connect(self.run_command_on_ssh)
            # commands_groupbox_layout.addWidget(self.command_exec)

            self.root_command_exec = QPushButton('Выполнить команду от root')
            self.root_command_exec.clicked.connect(self.run_root_command_on_ssh)
            commands_groupbox_layout.addWidget(self.root_command_exec)

            self.install_programs_button = QPushButton('Установить программы')
            self.install_programs_button.clicked.connect(self.install_programs)
            commands_groupbox_layout.addWidget(self.install_programs_button)

            self.remove_programs_button = QPushButton('Удалить программы')
            self.remove_programs_button.clicked.connect(self.remove_programs)
            commands_groupbox_layout.addWidget(self.remove_programs_button)

            self.enable_remote_login_button = QPushButton('Включить удалённый вход в систему')
            self.enable_remote_login_button.clicked.connect(self.enable_remote_login)
            commands_groupbox_layout.addWidget(self.enable_remote_login_button)

            # self.file_associations_btn = QPushButton('Файловые ассоциации')
            # self.file_associations_btn.clicked.connect(self.file_associations_func)
            # commands_groupbox_layout.addWidget(self.file_associations_btn)

            restrictions_groupbox = QWidget()
            restrictions_groupbox_layout = QVBoxLayout()
            restrictions_groupbox.setLayout(restrictions_groupbox_layout)
            # left_panel_layout.addWidget(restrictions_groupbox)

            self.restrict_desktop_change_btn = QPushButton('Запретить изменения рабочего стола')
            self.restrict_desktop_change_btn.clicked.connect(lambda: self.restrict_desktop_change(True))
            restrictions_groupbox_layout.addWidget(self.restrict_desktop_change_btn)

            self.allow_desktop_change_btn = QPushButton('Разрешить изменения рабочего стола')
            self.allow_desktop_change_btn.clicked.connect(lambda: self.restrict_desktop_change(False))
            restrictions_groupbox_layout.addWidget(self.allow_desktop_change_btn)

            self.restrict_network_btn = QPushButton('Отключить интернет на хостах')
            self.restrict_network_btn.clicked.connect(lambda: self.restrict_network(True))
            restrictions_groupbox_layout.addWidget(self.restrict_network_btn)

            self.allow_network_btn = QPushButton('Включить интернет на хостах')
            self.allow_network_btn.clicked.connect(lambda: self.restrict_network(False))
            restrictions_groupbox_layout.addWidget(self.allow_network_btn)

            self.password_protect_btn = QPushButton('Защитить настройки паролем')
            self.password_protect_btn.clicked.connect(lambda: self.password_protect(True))
            restrictions_groupbox_layout.addWidget(self.password_protect_btn)

            self.password_remove_btn = QPushButton('Снять защиту паролем')
            self.password_remove_btn.clicked.connect(lambda: self.password_protect(False))
            restrictions_groupbox_layout.addWidget(self.password_remove_btn)

            self.stop_threads = QPushButton('Прервать выполнение команды')
            self.stop_threads.clicked.connect(self.stop_threads_function)
            self.stop_threads.setIcon(QIcon('/usr/share/icons/elementary/actions/48/process-stop.svg'))
            left_panel_layout.addWidget(self.stop_threads)

            self.treeview_model = QStandardItemModel(0, 1)
            self.treeview.setModel(self.treeview_model)

            root_node = self.treeview_model.invisibleRootItem()

            delegate = StyledItemDelegate(self.treeview)
            self.treeview.setItemDelegate(delegate)

            treeview_headers = []
            row = 0
            self.treeview_widgets = (
                network_groupbox,
                users_groupbox,
                veyon_groupbox,
                commands_groupbox,
                restrictions_groupbox
            )
            for header_name in (
                    "Сеть",
                    "Пользователи",
                    "Veyon",
                    "Выполнение команд",
                    "Ограничения профиля"
            ):
                treeview_headers.append(QStandardItem(header_name))
                treeview_headers[row].setEditable(False)
                root_node.setChild(row, 0, treeview_headers[row])
                self.treeview_model.item(row, 0).setChild(0, 0, QStandardItem())
                self.treeview_model.item(row, 0).setIcon(QApplication.style().standardIcon(QStyle.SP_ArrowRight))
                self.treeview.setIndexWidget(self.treeview_model.item(row, 0).child(0, 0).index(),
                                             self.treeview_widgets[row])
                row += 1

            self.treeview.setHeaderHidden(True)
            self.treeview.setExpandsOnDoubleClick(True)
            self.treeview.setItemsExpandable(True)
            self.treeview.setIndentation(0)
            self.treeview.clicked.connect(self.expand_treeview_by_click)

            left_panel_layout.addWidget(self.treeview)
            expanded_widgets = run_command(f'py-ini-config get {config_path}/ccs.conf Main expanded_items')
            if expanded_widgets:
                expanded_widgets = bin(int(expanded_widgets))[2:].zfill(len(self.treeview_widgets))
                for i in range(len(expanded_widgets)):
                    if expanded_widgets[i] == '1':
                        self.treeview.expand(self.treeview_model.item(i, 0).index())
                        self.treeview_model.item(i, 0).setIcon(QApplication.style().standardIcon(QStyle.SP_ArrowDown))

            self.all_buttons = []
            for widget in self.treeview_widgets:
                for button in widget.children():
                    if type(button) is QPushButton and button.text() != "Создать ещё один сервер":
                        self.all_buttons.append(button)

            empty_place = QWidget()
            left_panel_layout.addWidget(empty_place)

            if len(self.hosts.items_to_list()) == 0:
                self.set_buttons_status(False)

            self.hosts_table.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive)
            self.hosts_table.horizontalHeader().setStretchLastSection(QHeaderView.Stretch)
            # self.hosts_table.setEditTriggers(QAbstractItemView.CurrentChanged)
            self.hosts_table.setEditTriggers(QAbstractItemView.AllEditTriggers)
            self.hosts_table.horizontalHeader().sectionClicked.connect(self.select_comps)
            self.hosts_table.verticalHeader().setSectionResizeMode(QHeaderView.Fixed)
            # settings_window_menu_bar = QMenuBar()
            # settings_menu = settings_window_menu_bar.addMenu('Меню')
            # import_from_file_menu = settings_menu.addAction('Открыть файл')
            # get_known_hosts_menu = settings_menu.addAction('Импорт из конфига')
            # import_from_file_menu.triggered.connect(self.open_file_dialog)
            # get_known_hosts_menu.triggered.connect(self.import_known_hosts)
            # grid.setMenuBar(settings_window_menu_bar)
            self.commands_history = []

    def expand_treeview(self) -> None:
        """
        Устанавливает значки на древовидную структуру вкладки настроек,
        сохраняет в файл конфига её состояние
        """
        item = self.treeview_model.itemFromIndex(self.sender().currentIndex())
        if self.treeview.isExpanded(self.sender().currentIndex()):
            item.setIcon(QApplication.style().standardIcon(QStyle.SP_ArrowDown))
        else:
            item.setIcon(QApplication.style().standardIcon(QStyle.SP_ArrowRight))
        # Индексы развёрнутых пунктов хранятся как двоичное число
        expanded_items_to_store = 0
        for row in range(len(self.treeview_widgets)):
            if self.treeview.isExpanded(self.treeview_model.item(row, 0).index()):
                expanded_items_to_store += 1
            expanded_items_to_store *= 2
        run_command(f'py-ini-config set {config_path}/ccs.conf Main expanded_items {expanded_items_to_store // 2}')

    def expand_treeview_by_click(self) -> None:
        """
        Обработка нажатия мышью на древовидную структуру вкладки настроек
        """
        if self.treeview.isExpanded(self.treeview.currentIndex()):
            self.treeview.collapse(self.treeview.currentIndex())
        else:
            self.treeview.expand(self.treeview.currentIndex())
        self.expand_treeview()

    def show_import_menu(self) -> None:
        """
        Показывает контекстное меню при импорте списка хостов
        """
        self.textfield.clear()
        self.textfield.appendPlainText('Внимание! Импорт хостов добавит информацию из файла в таблицу. '
                                       'Если вы хотите заменить список хостов, сначала выполните команду Очистить.')
        import_menu = QMenu()
        text_action = QAction("Из текстового файла (только адреса)")
        json_action = QAction("Из JSON (все настройки)")
        text_action.triggered.connect(self.import_from_txt)
        json_action.triggered.connect(self.import_from_json)
        import_menu.addAction(text_action)
        import_menu.addAction(json_action)
        import_menu.exec(self.cursor().pos())

    def generate_admins(self) -> None:
        """
        Заполняет логины и пароли админов на выбранных хостах указанными значениями
        """
        get_data_window = users_admins_generator(True)
        if get_data_window.exec() == QDialog.Accepted:
            name, passwd = get_data_window.name.text(), get_data_window.password.text()
            for row in range(self.hosts_table.rowCount()):
                if self.hosts_table.cellWidget(row, 0).state():
                    if self.hosts_table.item(row, 6):
                        self.hosts_table.item(row, 6).setText(name)
                    else:
                        self.hosts_table.setItem(row, 6, QTableWidgetItem(name))
                    if self.hosts_table.item(row, 7):
                        self.hosts_table.item(row, 7).setText(passwd)
                    else:
                        self.hosts_table.setItem(row, 7, QTableWidgetItem(passwd))

    def generate_users(self) -> None:
        """
        Заполняет логины и пароли учеников на выбранных хостах указанными значениями
        """
        get_data_window = users_admins_generator(False)
        if get_data_window.exec() == QDialog.Accepted:
            name, passwd = get_data_window.name.text(), get_data_window.password.text()
            for row in range(self.hosts_table.rowCount()):
                if self.hosts_table.cellWidget(row, 0).state():
                    if self.hosts_table.item(row, 4):
                        self.hosts_table.item(row, 4).setText(name)
                    else:
                        self.hosts_table.setItem(row, 4, QTableWidgetItem(name))
                    if self.hosts_table.item(row, 5):
                        self.hosts_table.item(row, 5).setText(passwd)
                    else:
                        self.hosts_table.setItem(row, 5, QTableWidgetItem(passwd))

    def set_buttons_status(self, status: bool) -> None:
        """
        Выключает доступность кнопок на время выполнения команд или включает обратно
        @param status: True, если нужно сделать кнопки доступными, иначе False
        """
        for button in self.all_buttons:
            button.setEnabled(status)

    def update_data(self, key: str = '') -> None:
        """
        Обновляет данные в таблице
        @param key: 'load', если таблица была пустой, иначе ''
        """
        self.hosts_table.blockSignals(True)
        font = QFont()
        font.setUnderline(True)
        font_ip = QFont()
        font_ip.setItalic(True)
        self.hosts_table.setHorizontalHeaderLabels(["Выбор", "Название", "Адрес", 'MAC-адрес', 'Логин ученика',
                                                    'Пароль ученика', 'Логин админа', 'Пароль админа', "Настроен",
                                                    "Удалённый логин"])
        self.hosts_table.setRowCount(len(self.hosts))

        if key == 'load':
            for index, host in enumerate(self.hosts.items_to_list()):
                item_name = QTableWidgetItem(host.name)
                item_hostname = QTableWidgetItem(host.hostname)
                if re.match(hostname_expression, host.hostname):
                    item_hostname.setFont(font)
                if re.match(ip_expression, host.hostname):
                    item_hostname.setFont(font_ip)
                self.hosts_table.setItem(index, 1, item_name)
                self.hosts_table.setItem(index, 2, item_hostname)
                properties = [host.mac_address, host.student_login, host.student_pass, host.admin_login,
                              host.admin_pass, 'Да' if host.setup else 'Нет', 'Да' if host.remote_login else 'Нет']
                for i in range(len(properties)):
                    self.hosts_table.setItem(index, i + 3, QTableWidgetItem(str(properties[i])))
                    if i + 3 in (5, 7):
                        self.hosts_table.item(index, i + 3).setText(
                            '*' * len(self.hosts_table.item(index, i + 3).text()))
                        self.hosts_table.item(index, i + 3).setToolTip(
                            host.student_pass if i + 3 == 5 else host.admin_pass
                        )
                self.hosts_table.item(index, 8).setFlags(Qt.ItemIsEnabled)
                self.hosts_table.item(index, 9).setFlags(Qt.ItemIsEnabled)
                item = TableCheckBox()
                self.hosts_table.setCellWidget(index, 0, item)
                self.hosts_table.cellWidget(index, 0).set_state(2 if host.checked else 0)
                self.hosts_table.cellWidget(index, 0).checkbox.stateChanged.connect(
                    lambda: self.change_data(self.hosts_table.item(10000, 3)))

        for row in range(self.hosts_table.rowCount()):
            if self.hosts_table.item(row, 2):
                if re.match(ip_expression, self.hosts_table.item(row, 2).text()):
                    self.hosts_table.item(row, 2).setFont(font_ip)
                elif self.hosts_table.item(row, 2).text() != '':
                    self.hosts_table.item(row, 2).setText(
                        self.hosts_table.item(row, 2).text().replace(suffix, '') + suffix)
                    self.hosts_table.item(row, 2).setFont(font)
            if self.hosts_table.item(row, 5):
                self.hosts_table.item(row, 5).setToolTip(self.hosts[self.hosts_table.item(row, 1).text()].student_pass)
                self.hosts_table.item(row, 5).setText('*' * len(self.hosts_table.item(row, 5).text()))
            if self.hosts_table.item(row, 7):
                self.hosts_table.item(row, 7).setToolTip(self.hosts[self.hosts_table.item(row, 1).text()].admin_pass)
                self.hosts_table.item(row, 7).setText('*' * len(self.hosts_table.item(row, 7).text()))
        self.hosts_table.blockSignals(False)

    def change_data(self, table_item: QTableWidgetItem) -> None:
        """
        Реагирует на изменение данных в таблице
        @param table_item: элемент таблицы, который был изменён
        @return: None
        """

        # FIXME это очень плохое решение
        if not table_item:  # row = 10000, to update checked
            for row in range(self.hosts_table.rowCount()):
                if self.hosts[self.hosts_table.item(row, 1).text()].checked \
                        is not self.hosts_table.cellWidget(row, 0).state():
                    self.hosts.set_checked(
                        self.hosts_table.item(row, 1).text(), self.hosts_table.cellWidget(row, 0).state())
            return
        item_index = table_item.row()
        key = self.hosts_table.item(item_index, 1).text()
        old_key = "Введите название" if item_index >= len(self.hosts.to_list()) else self.hosts.to_list()[item_index]
        other_keys = []
        for row in range(self.hosts_table.rowCount()):
            if row != item_index and self.hosts_table.item(row, 1):
                other_keys.append(self.hosts_table.item(row, 1).text())
        done = True
        while key in other_keys:
            key, done = QInputDialog.getText(
                self, 'Внимание', f'Компьютер {key} уже есть в списке.\nВведите новое имя: ')
        if not key:
            key = 'Введите название'
            while key in other_keys or key == '':
                key = get_name_with_next_number(key)
        if not done:
            key = old_key
            if key == "Введите название":
                key = ""
        hostname = self.hosts_table.item(item_index, 2).text() if self.hosts_table.item(item_index, 2) else ""
        if not re.match(ip_expression, hostname):
            hostname = hostname.replace(suffix, '') + suffix
        checked = self.hosts_table.cellWidget(item_index, 0).state() if self.hosts_table.cellWidget(item_index, 0) \
            else True
        mac_address = self.hosts_table.item(item_index, 3).text() if self.hosts_table.item(item_index, 3) else ""
        student_login = self.hosts_table.item(item_index, 4).text() if self.hosts_table.item(item_index, 4) else ""
        student_pass = self.hosts_table.item(item_index, 5).text() if table_item.column() == 5 else \
            (self.hosts[key].student_pass if key in self.hosts.to_list() else
             (self.hosts[old_key].student_pass if old_key in self.hosts.to_list() else ""))
        admin_login = self.hosts_table.item(item_index, 6).text() if self.hosts_table.item(item_index, 6) else ""
        admin_pass = self.hosts_table.item(item_index, 7).text() if table_item.column() == 7 else \
            (self.hosts[key].admin_pass if key in self.hosts.to_list() else
             (self.hosts[old_key].admin_pass if old_key in self.hosts.to_list() else ""))
        setup = True if (self.hosts_table.item(item_index, 8) and self.hosts_table.item(item_index, 8).text() == 'Да'
                         and table_item.column() != 2) \
            else (True if hostname in self.hosts.setup_hosts and
                          self.hosts.setup_hosts[hostname]['setup'] is True else False)
        remote_login = True if (self.hosts_table.item(item_index, 9) and
                                self.hosts_table.item(item_index, 9).text() == 'Да'
                                and table_item.column() != 2) \
            else (True if hostname in self.hosts.setup_hosts and
                          self.hosts.setup_hosts[hostname]['remote_login'] is True else False)
        if key != '':
            self.hosts.set_item(key, hostname, mac_address, student_login, student_pass, admin_login, admin_pass, setup,
                                remote_login, checked)
            if self.hosts_table.item(table_item.row(), 8):
                self.hosts_table.item(table_item.row(), 8).setText('Да' if self.hosts[key].setup else 'Нет')
            if self.hosts_table.item(table_item.row(), 9):
                self.hosts_table.item(table_item.row(), 9).setText('Да' if self.hosts[key].remote_login else 'Нет')
        for host in self.hosts.items_to_list():
            if host.name not in other_keys + [key]:
                del self.hosts[host.name]
        if table_item.column() == 1:
            hosts_hostnames = self.hosts.to_list()
            for i in range(table_item.row(), len(hosts_hostnames) - 1):
                self.hosts.hosts.move_to_end(hosts_hostnames[i])
            self.hosts_table.item(table_item.row(), 1).setText(key)
        if table_item.column() == 2:
            if setup:
                self.hosts.set_true_setup(key)
            else:
                self.hosts.set_false_setup(key)
        self.update_data(table_item)

    def add_row(self) -> None:
        """
        Добавление пустой строки в список хостов
        """
        hostnames = [host.name for host in self.hosts.items_to_list()]
        number = self.hosts_table.rowCount()
        item_name = "Введите название"
        if self.hosts_table.currentItem() and self.hosts_table.currentColumn() == 1:
            current_name = self.hosts_table.currentItem().text()
            item_name = get_name_with_next_number(current_name)

        while item_name in hostnames:
            item_name = get_name_with_next_number(item_name)
        self.hosts_table.setRowCount(number + 1)
        self.hosts_table.setItem(number, 1, QTableWidgetItem(item_name))
        self.hosts_table.setItem(number, 8, QTableWidgetItem('Нет'))
        self.hosts_table.setItem(number, 9, QTableWidgetItem('Нет'))
        self.hosts_table.item(number, 8).setFlags(Qt.ItemIsEnabled)
        self.hosts_table.item(number, 9).setFlags(Qt.ItemIsEnabled)
        self.hosts_table.setCellWidget(number, 0, TableCheckBox())
        self.hosts_table.cellWidget(number, 0).set_state(2)
        self.hosts_table.cellWidget(number, 0).checkbox.stateChanged.connect(
            lambda: self.change_data(self.hosts_table.item(10000, 3)))
        self.hosts_table.setCurrentCell(number, 1)
        self.set_buttons_status(True)

    def delete_row(self) -> None:
        """
        Удаление строки из списка хостов с удалением данного хоста из файла конфига
        """
        row = self.hosts_table.currentRow()
        if row < 0:
            return
        else:
            key = self.hosts_table.item(row, 1).text()
            messageBox = QMessageBox.warning(
                self,
                "Подтверждение удаления!",
                f"Вы действительно хотите удалить {key}?",
                QMessageBox.Ok | QMessageBox.Cancel,
            )
            if messageBox == QMessageBox.Ok:
                if key in self.hosts.hosts:
                    del self.hosts[key]
                self.hosts_table.removeRow(row)
        if len(self.hosts.items_to_list()) == 0:
            self.set_buttons_status(False)

    def delete_all(self) -> None:
        """
        Очистка таблицы хостов и файла конфига со списком хостов
        """
        message_box = QMessageBox.warning(
            self,
            "Подтверждение удаления!",
            f"Вы действительно хотите очистить список устройств?",
            QMessageBox.Ok | QMessageBox.Cancel,
        )
        if message_box == QMessageBox.Ok:
            self.hosts.clean()
            self.update_data(key='load')
            self.set_buttons_status(False)

    def select_comps(self, item: int) -> None:
        """
        Функция для выбора всех хостов или снятия выбора.
        Если хотя бы один компьютер не был выбран, выбирает все, иначе снимает выбор.
        @param item: номер столбца, функция работает, если 0
        """
        if item == 0:
            status = 0
            for i in range(len(self.hosts.items_to_list())):
                if self.hosts_table.cellWidget(i, 0).state() == 0:
                    status = 2
                    break
            for i in range(len(self.hosts.items_to_list())):
                self.hosts_table.cellWidget(i, 0).set_state(status)

    def import_from_txt(self) -> None:
        """
        Импортирует список адресов хостов из текстового файла
        @return: None
        """

        file_name = run_command("kdialog --getopenfilename --title='Выберите текстовый файл' ~ 'text/plain'").strip()
        try:
            with open(file_name, 'r') as inp:
                lines = inp.readlines()
                if len(lines) > 100:
                    QMessageBox('Слишком большой файл!').show()
                else:
                    universal_name, okPressed = QInputDialog.getText(
                        self, "Имя компьютера", "Введите универсальное имя для компьютеров в этом кабинете: ",
                        QLineEdit.Normal, "Компьютер")
                    if not okPressed:
                        return
                    comp_number = 1
                    reading_errors = False
                    errors_cnt = 0
                    cnt = 0
                    existing_rows_counter = self.hosts_table.rowCount()
                    for index, host in enumerate(lines, start=existing_rows_counter):
                        if host.strip() == '' or ' ' in host.strip():
                            if ' ' in host.strip():
                                reading_errors = True
                                errors_cnt += 1
                            continue
                        right_host_address = host.strip()
                        if not re.match(hostname_expression, right_host_address):
                            right_host_address = right_host_address.replace(suffix, '') + suffix
                        is_host_setup = True if right_host_address in self.hosts.setup_hosts and \
                                                self.hosts.setup_hosts[right_host_address] is True else False
                        new_index = existing_rows_counter + cnt
                        self.hosts_table.setRowCount(new_index + 1)
                        while f'{universal_name}{comp_number}' in self.hosts.to_list():
                            comp_number += 1
                        self.hosts_table.setItem(new_index, 1, QTableWidgetItem(f"{universal_name}"
                                                                                f"{comp_number}"))
                        comp_number += 1
                        self.hosts_table.setItem(new_index, 2, QTableWidgetItem(right_host_address))
                        self.hosts_table.setItem(new_index, 8, QTableWidgetItem(
                            "Да" if is_host_setup else "Нет"
                        ))
                        self.hosts_table.item(new_index, 8).setFlags(Qt.ItemIsEnabled)
                        self.hosts_table.setItem(new_index, 9, QTableWidgetItem("Нет"))
                        self.hosts_table.item(new_index, 9).setFlags(Qt.ItemIsEnabled)
                        self.hosts_table.setCellWidget(new_index, 0, TableCheckBox())
                        self.hosts_table.cellWidget(new_index, 0).set_state(2)
                        self.hosts_table.cellWidget(new_index, 0).checkbox.stateChanged.connect(
                            lambda: self.change_data(self.hosts_table.item(10000, 3)))
                        cnt += 1

                    self.textfield.clear()
                    self.textfield.appendPlainText(f'\nИмпортированы адреса компьютеров из текстового файла\n')

                    if reading_errors:
                        self.textfield.appendHtml(f'<font color="red">Успешно: {cnt} из {cnt + errors_cnt} строк '
                                                  f'файла</font>')
                        self.textfield.appendPlainText('При чтении адресов возникли ошибки, '
                                                       'проверьте корректность написания адресов.')
                    else:
                        self.textfield.appendPlainText(f'Успешно: {cnt} из {cnt + errors_cnt} строк файла')
                    if cnt > 0:
                        self.set_buttons_status(True)
                    else:
                        self.set_buttons_status(False)
        except FileNotFoundError:
            pass
        except Exception as e:
            logging.info(f'Ошибка чтения файла {e}')
            self.textfield.clear()
            self.textfield.appendPlainText('Ошибка чтения выбранного файла')

    def export_json(self, file_name='') -> None:
        """
        Экспортирует все данные из таблицы хостов в файл JSON.
        @param file_name: имя файла для экспорта
        """

        if not file_name:
            file_name = run_command('kdialog --getsavefilename --title="Введите имя файла" ~').strip()
        if file_name:
            if not file_name.endswith('.json'):
                file_name += '.json'
            run_command(f'cp "{hosts_file_path}" "{file_name}"')
            self.textfield.appendPlainText(f'Сохранено в {file_name}.')

    def import_from_json(self, file_name='', delete_existing=False):
        """
        Импортирует все данные в таблицу целиком из файла JSON.
        Выполнение данного действия возможно только в случае,
        если файл для импорта был создан экспортом из данной программы.
        @param file_name: имя файла для импорта
        @param delete_existing: если True, то хосты в таблице заменяются импортированными, иначе они добавляются
        @return: None
        """

        if not file_name:
            file_name = run_command(
                "kdialog --getopenfilename --title='Выберите файл JSON' ~ 'application/json'").strip()
        try:
            with open(file_name, 'r') as inp:
                contents = json.loads(inp.read())
            for host in contents:
                keys = set(contents[host].keys())
                if keys != {
                    "name",
                    "hostname",
                    "mac_address",
                    "student_login",
                    "student_pass",
                    "admin_login",
                    "admin_pass",
                    "setup",
                    "remote_login",
                    "checked"
                }:
                    dlg = QMessageBox(self)
                    dlg.setWindowTitle("Ошибка")
                    dlg.setText("Выбранный файл не был создан данной программой")
                    dlg.exec()
                    return
            if delete_existing:
                self.hosts.clean()
            for host in contents:
                self.hosts.set_item(
                    contents[host]['name'],
                    contents[host]['hostname'],
                    contents[host]['mac_address'],
                    contents[host]['student_login'],
                    contents[host]['student_pass'],
                    contents[host]['admin_login'],
                    contents[host]['admin_pass'],
                    contents[host]['setup'],
                    contents[host]['remote_login'],
                    contents[host]['checked']
                )
            self.hosts_table.clear()
            self.update_data(key='load')
            if self.hosts_table.rowCount() > 0:
                self.set_buttons_status(True)
            else:
                self.set_buttons_status(False)

        except FileNotFoundError:
            pass
        except Exception as e:
            logging.info(f'Ошибка чтения файла {e}')
            self.textfield.clear()
            self.textfield.appendPlainText('Ошибка чтения выбранного файла')

    # def import_known_hosts(self):
    #     print('importing')
    #     self.known_hosts_window = KnownHostsManagement()
    #     self.known_hosts_window.show()

    def update_textfield(self, message: str) -> None:
        """
        Обновляет текстовое поле в нижней части окна настроек
        @param message: сообщение для добавления в текстовое поле
        """
        if '<br>' in message or '<font' in message:
            message = message.replace('\n', '<br>')
            self.textfield.appendHtml(message)
        else:
            self.textfield.appendPlainText(message)

    def test_ping(self) -> None:
        """
        Проверка доступности хостов
        """
        self.textfield.clear()

        self.thread = PingTest()
        host_list = []
        for index, host in enumerate(self.hosts.items_to_list()):
            if self.hosts_table.cellWidget(index, 0).state():
                host_list.append(host)
        self.thread.host_list = host_list

        self.textfield.appendPlainText(f"\nПроверка ping, выбрано компьютеров: {len(host_list)}")
        self.thread.progress_signal.connect(self.update_textfield)
        self.thread.finished.connect(self.thread.deleteLater)
        self.thread.start()

        self.set_buttons_status(False)
        self.thread.finished.connect(
            lambda: self.set_buttons_status(True)
        )

    def setup_ssh(self) -> None:
        """
        Настройка ssh и копирование ключей на хосты
        @return: None
        """
        self.textfield.clear()
        messageBox = QMessageBox.information(
            self,
            "Важная информация!",
            f"Вы запустили настройку ssh для пользователей из группы wheel на компьютерах, указанных в таблице.\n\n"
            f"Во время первичной настройки будет осуществляться подключение к компьютерам "
            f"и сохранение ключей аутентификации для пользователя из wheel.\n",
            QMessageBox.Ok | QMessageBox.Cancel,
        )
        if messageBox == QMessageBox.Ok:
            self.thread = SSHRootSetup()

            host_list = []
            for index, host in enumerate(self.hosts.items_to_list()):
                if self.hosts_table.cellWidget(index, 0).state():
                    host_list.append(host)
            self.thread.host_list = host_list
            self.thread.hosts = self.hosts

            dlg = QMessageBox(self)
            dlg.setWindowTitle("Замена ключей")
            dlg.setText("Заменить ключи на компьютерах? Ответ НЕТ позволит добавить ключи к существующим.")
            dlg.setStandardButtons(QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
            dlg.button(QMessageBox.Yes).setText('Да')
            dlg.button(QMessageBox.No).setText('Нет')
            dlg.button(QMessageBox.Cancel).setText('Отмена')
            dlg.setIcon(QMessageBox.Question)
            button = dlg.exec()

            if button == QMessageBox.Yes:
                self.thread.replace_keys = True
            elif button == QMessageBox.No:
                self.thread.replace_keys = False
            else:
                return

            self.textfield.appendPlainText(f"\nНастройка ssh, выбрано компьютеров: {len(host_list)}")
            self.thread.progress_signal.connect(self.update_textfield)
            self.thread.delete_known_hosts_signal.connect(self.delete_known_hosts_func)
            self.thread.finished.connect(self.thread.deleteLater)
            self.thread.start()

            self.set_buttons_status(False)
            self.thread.finished.connect(
                lambda: self.set_buttons_status(True)
            )
            self.thread.finished.connect(self.refill_table)

    def delete_ssh(self) -> None:
        """
        Удаление ключей ssh с хостов
        """
        self.textfield.clear()
        messageBox = QMessageBox.information(
            self,
            "Внимание!",
            f"Вы собираетесь удалить ключи ssh на компьютерах пользователей",
            QMessageBox.Ok | QMessageBox.Cancel,
        )
        if messageBox == QMessageBox.Ok:
            self.thread = SSHDelete()

            host_list = []
            for index, host in enumerate(self.hosts.items_to_list()):
                if self.hosts_table.cellWidget(index, 0).state():
                    host_list.append(host)
            self.thread.host_list = host_list
            self.thread.hosts = self.hosts

            self.thread.progress_signal.connect(self.update_textfield)
            self.thread.finished.connect(self.thread.deleteLater)
            self.thread.start()

            self.set_buttons_status(False)
            self.thread.finished.connect(
                lambda: self.set_buttons_status(True)
            )
            self.thread.finished.connect(self.refill_table)

    def delete_known_hosts_func(self) -> None:
        """
        Удаление адресов хостов из known_hosts на сервере
        """
        dlg = QMessageBox()
        dlg.setWindowTitle("Внимание!")
        dlg.setText(f"Похоже, что вы не смогли подключиться как минимум к одному компьютеру по ssh.\n"
                    f"Если вы уверены в правильности логинов и паролей админов, возможно, проблема в файле "
                    f"known_hosts на компьютере учителя. Если хост был настроен ранее и сохранён как известный, "
                    f"а потом перенастроен, его наличие в файле known_hosts может мешать подключению по ssh.\n"
                    f"Удалить отмеченные хосты из known_hosts?")
        dlg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
        dlg.button(QMessageBox.Yes).setText('Да')
        dlg.button(QMessageBox.No).setText('Нет')
        button = dlg.exec()
        if button == QMessageBox.Yes:
            for host in self.hosts.items_to_list():
                if host.checked:
                    res = run_command(f'ssh-keygen -R {host.hostname}')
                    logging.info(f'Результат удаления {host.hostname} из known_hosts: {res}')
            self.textfield.appendPlainText('Указанные хосты удалены из known_hosts.')

    def create_student(self) -> None:
        """
        Создание учёток ученика на хостах
        """
        self.textfield.clear()

        self.thread = СreateStudent()
        host_list = []
        for index, host in enumerate(self.hosts.items_to_list()):
            if self.hosts_table.cellWidget(index, 0).state():
                host_list.append(host)
        self.thread.host_list = host_list
        self.thread.hosts = self.hosts

        self.thread.start_signal.connect(self.update_textfield)
        self.thread.progress_signal.connect(self.update_textfield)
        self.thread.finish_signal.connect(self.thread.deleteLater)
        self.thread.start()

        self.set_buttons_status(False)
        self.thread.finished.connect(
            lambda: self.set_buttons_status(True)
        )

    def delete_student(self) -> None:
        """
        Удаление учёток ученика на хостах
        """
        self.textfield.clear()

        self.thread = DeleteStudent()
        hosts_list = []
        for index, host in enumerate(self.hosts.items_to_list()):
            if self.hosts_table.cellWidget(index, 0).state():
                hosts_list.append(host)
        self.thread.hosts_list = hosts_list
        self.thread.hosts = self.hosts

        self.thread.start_signal.connect(self.update_textfield)
        self.thread.progress_signal.connect(self.update_textfield)
        self.thread.finish_signal.connect(self.update_textfield)
        self.thread.finish_signal.connect(self.thread.deleteLater)
        self.thread.start()

        self.set_buttons_status(False)
        self.thread.finished.connect(
            lambda: self.set_buttons_status(True)
        )

    # def network_folders(self):
    #     self.textfield.clear()
    #     messageBox = QMessageBox.information(
    #         self,
    #         "Важная информация!",
    #         f"Вы запустили настройку сетевой папки.\n"
    #         f"На компьютере учителя будет создана папка /home/share, "
    #         f"к которой будет настроен доступ по протоколу smb.",
    #         QMessageBox.Ok | QMessageBox.Cancel,
    #     )
    #     if messageBox == QMessageBox.Ok:
    #         self.thread = NetworkFolderSetup()
    #         # self.thread.hosts = self.hosts
    #         host_list = []
    #         for index, host in enumerate(self.hosts.items_to_list()):
    #             if self.hosts_table.cellWidget(index, 0).state():
    #                 host_list.append(host)
    #         self.thread.host_list = host_list
    #
    #         self.textfield.appendPlainText(f"\nНастройка сетевых папок, выбрано компьютеров: {len(host_list)}\n")
    #         self.thread.progress_signal.connect(self.update_textfield)
    #         self.thread.finished.connect(self.thread.deleteLater)
    #         self.thread.start()
    #
    #         self.set_buttons_status(False)
    #         self.thread.finished.connect(
    #             lambda: self.set_buttons_status(True)
    #         )

    def get_ip_by_mac(self) -> None:
        """
        Получение IP хостов по MAC-адресу
        """
        dlg = QMessageBox()
        dlg.setWindowTitle("Внимание!")
        dlg.setText("Вы собираетесь заменить имена выбранных компьютеров на их IP-адреса. Данная операция является "
                    "необратимой, обратная замена возможна только вручную! Продолжить?")
        dlg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
        button = dlg.exec()
        if button == QMessageBox.Ok:
            self.textfield.clear()
            self.thread = GetIPByMac()
            self.thread.hosts = self.hosts
            host_list = []
            for index, host in enumerate(self.hosts.items_to_list()):
                if self.hosts_table.cellWidget(index, 0).state():
                    host_list.append(host)
            self.thread.host_list = host_list

            self.thread.started.connect(
                lambda: self.textfield.appendPlainText(
                    f"Определяем IP по MAC-адресам... "
                    f"выбрано компьютеров: {len(host_list)}")
            )
            self.thread.progress_signal.connect(self.update_textfield)
            self.thread.finished.connect(self.thread.deleteLater)
            self.thread.start()

            self.set_buttons_status(False)
            self.thread.finished.connect(
                lambda: self.set_buttons_status(True)
            )
            self.thread.finished.connect(self.refill_table)

    def install_veyon(self) -> None:
        """
        Устанавливает veyon на компьютере учителя и veyon-core на хостах
        """
        self.textfield.clear()
        self.thread = VeyonInstall()
        self.thread.hosts = self.hosts
        host_list = []
        for index, host in enumerate(self.hosts.items_to_list()):
            if self.hosts_table.cellWidget(index, 0).state():
                host_list.append(host)
        self.thread.host_list = host_list

        self.thread.started.connect(
            lambda: self.textfield.appendPlainText(
                f"\nНачинается установка Veyon, "
                f"выбрано компьютеров: {len(host_list)}.")
        )
        self.thread.progress_signal.connect(self.update_textfield)
        self.thread.finished.connect(self.thread.deleteLater)
        self.thread.start()

        self.set_buttons_status(False)
        self.thread.finished.connect(
            lambda: self.set_buttons_status(True)
        )
        self.thread.finished.connect(self.refill_table)

    def setup_veyon(self) -> None:
        """
        Настраивает кабинет и veyon на хостах
        """
        self.textfield.clear()
        kab, okPressed = QInputDialog.getText(self, "Номер кабинета",
                                              f"Внимание! Запущена настройка Veyon на компьютерах учителя и учеников. "
                                              f"После настройки каждый компьютер ученика будет перезагружен.\n"
                                              f"Введите номер этого кабинета (без пробелов):",
                                              QLineEdit.Normal, "")
        if okPressed:
            self.thread = VeyonSetup()
            self.thread.hosts = self.hosts
            host_list = []
            for index, host in enumerate(self.hosts.items_to_list()):
                if self.hosts_table.cellWidget(index, 0).state():
                    host_list.append(host)
            self.thread.host_list = host_list
            self.thread.kab = kab

            self.thread.started.connect(
                lambda: self.textfield.appendPlainText(
                    f"\nНачинается настройка veyon, "
                    f"выбрано компьютеров: {len(host_list)}.\n")
            )
            self.thread.progress_signal.connect(self.update_textfield)
            self.thread.computer_name_conflict_signal.connect(self.show_conflict_compname_msgbox,
                                                              Qt.BlockingQueuedConnection)
            self.thread.cabinet_conflict_signal.connect(self.show_conflict_cabinet_msgbox, Qt.BlockingQueuedConnection)
            self.thread.finished.connect(self.thread.deleteLater)
            self.thread.setTerminationEnabled(True)
            self.thread.start()

            self.set_buttons_status(False)
            self.thread.finished.connect(
                lambda: self.set_buttons_status(True)
            )

    def remove_veyon_keys_on_server(self) -> None:
        """
        Удаление ключей вейон на сервере
        @return: None
        """
        found_keys = run_command('veyon-cli authkeys list').strip().split('\n')
        while '' in found_keys:
            found_keys.remove('')
        if not found_keys:
            self.textfield.appendPlainText('На сервере не найдены ключи Veyon.')
            return
        dlg = QMessageBox()
        dlg.setWindowTitle("Внимание!")
        dlg.setText(f"Найдены ключи: {', '.join(found_keys)}. Удалить?")
        dlg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
        if dlg.exec() == QMessageBox.Ok:
            commands = [f'veyon-cli authkeys delete {key}' for key in found_keys]
            command = '; '.join(commands)
            try:
                res = run_command_by_root(command)
                if 'Сбой при проверке подлинности' in res:
                    self.textfield.appendPlainText('Неверный пароль root.')
                else:
                    self.textfield.appendPlainText(f"Ключи {', '.join(found_keys)} удалены.")
            except Exception as e:
                self.textfield.appendPlainText(f'Не удалось удалить ключи с сервера. Ошибка: {e}')
                logging.info(f'Не удалось удалить ключи с сервера. Ошибка: {e}')

    def remove_veyon_keys_on_hosts(self) -> None:
        """
        Удаление ключей вейон на хостах
        """
        dlg = QMessageBox()
        dlg.setWindowTitle("Внимание!")
        dlg.setText(f"Удалить все найденные ключи Veyon на выбранных хостах?")
        dlg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
        if dlg.exec() == QMessageBox.Ok:
            self.thread = VeyonRemoveKeys()
            host_list = []
            for index, host in enumerate(self.hosts.items_to_list()):
                if self.hosts_table.cellWidget(index, 0).state():
                    host_list.append(host)
            self.thread.host_list = host_list

            self.thread.started.connect(
                lambda: self.textfield.appendPlainText(
                    f"\nНачинается удаление ключей veyon, "
                    f"выбрано компьютеров: {len(host_list)}.\n")
            )
            self.thread.progress_signal.connect(self.update_textfield)
            self.thread.finish_signal.connect(self.update_textfield)
            self.thread.finished.connect(self.thread.deleteLater)
            self.thread.start()

            self.set_buttons_status(False)
            self.thread.finished.connect(
                lambda: self.set_buttons_status(True)
            )

    def restart_veyon_on_server(self) -> None:
        """
        Перезапуск службы Veyon на сервере
        """
        res = subprocess.run('systemctl restart veyon.service', shell=True).returncode
        if res == 0:
            self.update_textfield('Служба Veyon перезапущена.')

    def restart_veyon_on_hosts(self) -> None:
        """
        Перезапуск службы Veyon на отмеченных хостах
        """
        self.textfield.clear()

        self.thread = RestartVeyonOnHosts()
        host_list = []
        for index, host in enumerate(self.hosts.items_to_list()):
            if self.hosts_table.cellWidget(index, 0).state():
                host_list.append(host)
        self.thread.host_list = host_list

        self.thread.start_signal.connect(self.update_textfield)
        self.thread.progress_signal.connect(self.update_textfield)
        self.thread.finish_signal.connect(self.update_textfield)
        self.thread.start()

        self.set_buttons_status(False)
        self.thread.finished.connect(
            lambda: self.set_buttons_status(True)
        )

    # def add_another_server(self):
    #     select_new_server_window = WindowForSelectingNewServer()
    #     select_new_server_window.exec()

    def show_conflict_compname_msgbox(self, params: list) -> None:
        """
        Выбор действия в случае наличия компьютера в указанном кабинете при настройке veyon
        @param params: [имя хоста, номер кабинета, адрес хоста, MAC-адрес]
        """
        comp_name, kab, address, mac_address = params
        dlg = QMessageBox()
        dlg.setWindowTitle("Внимание!")
        dlg.setText(f"Компьютер {comp_name} уже есть в кабинете {kab}!\n"
                    f"Адрес: {address}, MAC-адрес: {mac_address}\n"
                    f"Выберите действие")
        dlg.setStandardButtons(QMessageBox.Yes | QMessageBox.YesAll | QMessageBox.No | QMessageBox.NoAll |
                               QMessageBox.Ok | QMessageBox.Ignore | QMessageBox.Cancel)
        dlg.button(QMessageBox.Yes).setText('Заменить')
        dlg.button(QMessageBox.YesAll).setText('Заменить все')
        dlg.button(QMessageBox.No).setText('Оставить текущий')
        dlg.button(QMessageBox.NoAll).setText('Оставить все')
        dlg.button(QMessageBox.Ok).setText('Добавить')
        dlg.button(QMessageBox.Ignore).setText('Добавить все')
        dlg.button(QMessageBox.Cancel).setText('Отмена')
        button = dlg.exec()
        self.thread.conflict_comp_button = button

    def show_conflict_cabinet_msgbox(self) -> None:
        """
        Выбор действия в случае наличия кабинета в конфигурации veyon
        """
        dlg = QMessageBox()
        dlg.setWindowTitle("Внимание!")
        dlg.setText(f"Кабинет {self.thread.kab} уже есть в конфигурации Veyon. Удалить его?\n"
                    f"Ответ НЕТ добавит компьютеры в существующий кабинет.")
        dlg.setStandardButtons(QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
        dlg.button(QMessageBox.Yes).setText('Да')
        dlg.button(QMessageBox.No).setText('Нет')
        dlg.button(QMessageBox.Cancel).setText('Отмена')
        dlg.setIcon(QMessageBox.Question)
        button = dlg.exec()
        self.thread.conflict_cabinet_button = button

    def refill_table(self) -> None:
        """
        Удаляет все данные из таблицы хостов и загружает заново из файла
        """
        self.hosts_table.clear()
        self.update_data(key='load')

    def run_root_command_on_ssh(self, install_requires=False, remove_requires=False) -> None:
        """
        Выполнение команды на хостах по ssh от root
        """
        self.textfield.clear()
        self.dialog = GetCommandForSSHRun(install_requires, remove_requires)
        self.dialog.combobox.addItems(self.commands_history)

        poweroff_required = False
        if self.dialog.exec_() == QDialog.Accepted:
            command = self.dialog.combobox.currentText()
            print(command)
            if command not in self.commands_history:
                self.commands_history = [command] + self.commands_history
            if (command == 'dnf --refresh -y up && poweroff' and
                self.dialog.sequence_poweroff_checkbox.isChecked()) or \
                    (command == "echo 'dnf --refresh -y up && poweroff' | at now" and
                     self.dialog.parallel_poweroff_checkbox.isChecked()):
                poweroff_required = True
            if (command == 'dnf --refresh -y up && poweroff' and
                self.dialog.sequence_poweroff_alert_checkbox.isChecked()) or \
                    (command == "echo 'dnf --refresh -y up && poweroff' | at now" and
                     self.dialog.parallel_poweroff_alert_checkbox.isChecked()) or \
                    (command == 'dnf --refresh -y up && reboot' and
                     self.dialog.sequence_restart_alert_checkbox.isChecked()) or \
                    (command == "echo 'dnf --refresh -y up && reboot' | at now" and
                     self.dialog.parallel_restart_alert_checkbox.isChecked()):
                command = command.replace('&&', '; pkill zenity;')

            selected = 0
            host_list = []
            for index, host in enumerate(self.hosts.items_to_list()):
                if self.hosts_table.cellWidget(index, 0).state():
                    host_list.append(host)
                    selected += 1

            self.thread = SSHCommandInThreads(host_list, command, True)
            self.thread.started.connect(
                lambda: self.textfield.appendPlainText(
                    f"Начало выполнения команды {command}\n"
                    f"Выбрано компьютеров: {len(host_list)}.\n"
                )
            )
            self.thread.progress_signal.connect(self.update_textfield)
            self.thread.finished.connect(self.thread.deleteLater)

            if 'pkill zenity' in command:
                self.show_alert_thread = ShowHostsNames()
                self.show_alert_thread.show_alert = True
                self.show_alert_thread.host_list = host_list
                self.show_alert_thread.finished.connect(self.show_alert_thread.deleteLater)
                self.show_alert_thread.finish_signal.connect(lambda: self.thread.start())
                self.show_alert_thread.start()

            else:
                self.thread.start()

            self.set_buttons_status(False)
            self.thread.finished.connect(
                lambda: self.set_buttons_status(True)
            )
            if poweroff_required:
                self.thread.finished.connect(poweroff)

    # def file_associations_func(self):
    #     dialog = WindowForSetFileAssociations()
    #     if dialog.exec_() == QDialog.Accepted:
    #         associations_to_set = dict()
    #         nrows = (dialog.layout.count() - 3) // 2
    #         for row in range(1, 1 + nrows):
    #             if dialog.layout.itemAtPosition(row, 1).widget().currentText() != "Не изменять":
    #                 associations_to_set[dialog.mimetype_params[dialog.layout.itemAtPosition(row, 0).widget().text()]] = \
    #                 dialog.association_dictionary[dialog.layout.itemAtPosition(row, 0).widget().text()][
    #                     dialog.layout.itemAtPosition(row, 1).widget().currentText()]
    #         # print(associations_to_set)
    #         """
    #         Ограничения изменения рабочего стола и виджетов
    #         """
    #         self.textfield.clear()
    #         self.thread = FileAssociationsThread()
    #         host_list = []
    #         for index, host in enumerate(self.hosts.items_to_list()):
    #             if self.hosts_table.cellWidget(index, 0).state():
    #                 host_list.append(host)
    #         self.thread.host_list = host_list
    #         self.thread.associations_to_set = associations_to_set
    #
    #         self.thread.start_signal.connect(self.update_textfield)
    #         self.thread.progress_signal.connect(self.update_textfield)
    #         self.thread.finish_signal.connect(self.update_textfield)
    #         self.thread.start()
    #
    #         self.set_buttons_status(False)
    #         self.thread.finished.connect(
    #             lambda: self.set_buttons_status(True)
    #         )

    def install_programs(self) -> None:
        """
        Вызывает окно для запуска команды установки программ по ssh
        """
        self.run_root_command_on_ssh(install_requires=True)

    def remove_programs(self):
        """
        Вызывает окно для запуска команды удаления программ по ssh
        """
        self.run_root_command_on_ssh(remove_requires=True)

    def autologin_enable_func(self) -> None:
        """
        Включение автологина ученика на хостах
        """
        self.textfield.clear()

        self.thread = Autologin(True)
        host_list = []
        for index, host in enumerate(self.hosts.items_to_list()):
            if self.hosts_table.cellWidget(index, 0).state():
                host_list.append(host)
        self.thread.host_list = host_list

        self.thread.start_signal.connect(self.update_textfield)
        self.thread.progress_signal.connect(self.update_textfield)
        self.thread.start()

        self.set_buttons_status(False)
        self.thread.finished.connect(
            lambda: self.set_buttons_status(True)
        )

    def autologin_disable_func(self) -> None:
        """
        Выключение автологина ученика на хостах
        """
        self.textfield.clear()

        self.thread = Autologin(False)
        host_list = []
        for index, host in enumerate(self.hosts.items_to_list()):
            if self.hosts_table.cellWidget(index, 0).state():
                host_list.append(host)
        self.thread.host_list = host_list

        self.thread.start_signal.connect(self.update_textfield)
        self.thread.progress_signal.connect(self.update_textfield)
        self.thread.start()

        self.set_buttons_status(False)
        self.thread.finished.connect(
            lambda: self.set_buttons_status(True)
        )

    def restrict_desktop_change(self, restrict=False):
        """
        Ограничения изменения рабочего стола и виджетов
        @param restrict: если True, включить ограничения, иначе снять
        """
        self.textfield.clear()
        self.thread = RestrictDesktopChanges(restrict)
        host_list = []
        for index, host in enumerate(self.hosts.items_to_list()):
            if self.hosts_table.cellWidget(index, 0).state():
                host_list.append(host)
        self.thread.host_list = host_list

        self.thread.start_signal.connect(self.update_textfield)
        self.thread.progress_signal.connect(self.update_textfield)
        self.thread.finish_signal.connect(self.update_textfield)
        self.thread.start()

        self.set_buttons_status(False)
        self.thread.finished.connect(
            lambda: self.set_buttons_status(True)
        )

    def restrict_network(self, restrict=False):
        """
        Отключение и включение интернета на хостах
        @param restrict: если True, выключить интернет, иначе включить
        """
        self.textfield.clear()
        self.thread = RestrictNetwork(restrict)
        host_list = []
        for index, host in enumerate(self.hosts.items_to_list()):
            if self.hosts_table.cellWidget(index, 0).state():
                host_list.append(host)
        self.thread.host_list = host_list

        self.thread.start_signal.connect(self.update_textfield)
        self.thread.progress_signal.connect(self.update_textfield)
        self.thread.finish_signal.connect(self.update_textfield)
        self.thread.start()

        self.set_buttons_status(False)
        self.thread.finished.connect(
            lambda: self.set_buttons_status(True)
        )

    def enable_remote_login(self) -> None:
        """
        Устанавливает на хостах mos-auth-dbus-conf и включает на них возможность авторизации через mos.ru
        """
        dlg = QMessageBox()
        dlg.setWindowTitle("Внимание!")
        dlg.setText(f"На выбранных хостах будет установлен пакет mos-auth-dbus-conf и включена авторизация "
                    f"через mos.ru. Также после этого компьютеры будут перезагружены. Выполнить включение возможности "
                    f"удалённого входа?")
        dlg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
        if dlg.exec() == QMessageBox.Ok:
            self.textfield.clear()
            self.thread = EnableRemoteLogin()
            host_list = []
            for index, host in enumerate(self.hosts.items_to_list()):
                if self.hosts_table.cellWidget(index, 0).state():
                    host_list.append(host)
            self.thread.host_list = host_list
            self.thread.hosts = self.hosts

            self.thread.start_signal.connect(self.update_textfield)
            self.thread.progress_signal.connect(self.update_textfield)
            self.thread.finish_signal.connect(self.update_textfield)
            self.thread.start()

            self.set_buttons_status(False)
            self.thread.finished.connect(
                lambda: self.set_buttons_status(True)
            )
            self.thread.finished.connect(self.refill_table)

    def password_protect(self, set_protect: bool) -> None:
        """
        Защита вкладки настроек паролем
        @param set_protect: если True, установить защиту, иначе снять
        """
        if set_protect:
            passwd1, okPressed = QInputDialog.getText(self, "Внимание!",
                                                      "Вы собираетесь защитить настройки хостов паролем.\n"
                                                      "При переходе на эту вкладку будет запрашиваться пароль.\n"
                                                      "Введите пароль:",
                                                      QLineEdit.Password, "")
            if okPressed:
                passwd2, okPressed2 = QInputDialog.getText(self, "Внимание!",
                                                           "Вы собираетесь защитить настройки хостов паролем.\n"
                                                           "При переходе на эту вкладку будет запрашиваться пароль.\n"
                                                           "Повторите пароль:",
                                                           QLineEdit.Password, "")
                if okPressed2:
                    if passwd2 != passwd1:
                        dlg = QMessageBox()
                        dlg.setWindowTitle("Ошибка")
                        dlg.setText("Введённые пароли не совпадают.")
                        dlg.exec()
                    else:
                        run_command(f'py-ini-config set {config_path}/ccs.conf Main protect_settings true')
                        password_md5 = run_command(f'echo -n "{passwd1}" | md5sum').strip()
                        run_command(f'echo "{password_md5}" > {config_path}/password_md5')
                        self.textfield.appendPlainText('Пароль установлен.')
                        logging.info(f'Установлен пароль на настройки, md5: {password_md5}')
        else:
            run_command(f'py-ini-config set {config_path}/ccs.conf Main protect_settings false')
            run_command(f'rm -rf {config_path}/password_md5')
            self.textfield.appendPlainText('Пароль снят.')
            logging.info('Пароль на настройки снят.')

    def expand_text_browser(self):
        sizes = self.right_panel_splitter.sizes()
        if sizes[1] == 0:
            height1 = run_command(f'py-ini-config get {config_path}/ccs.conf Main settings_tab_right_widget_height1')
            height2 = run_command(f'py-ini-config get {config_path}/ccs.conf Main settings_tab_right_widget_height2')
            if height1 and height2:
                self.right_panel_splitter.setSizes([int(height1), int(height2)])
            self.right_panel_splitter.setSizes([int(height1), int(height2)])

    def stop_threads_function(self) -> None:
        """
        Прерывает выполнение текущей команды
        """
        try:
            self.thread.terminate()
            self.textfield.appendPlainText('Остановлено.')
            logging.info(f'Поток {self.thread.metaObject().className()} остановлен.')
        except Exception as e:
            self.textfield.appendPlainText('Не удалось остановить процесс.')
            logging.info(f'При попытке остановки потока возникла ошибка: {e}')

    def set_nonzero_splitter_size(self) -> None:
        """
        Запоминает последнюю ненулевую высоту текстового поля на вкладке настройки хостов
        """
        height2 = self.right_panel_splitter.sizes()[1]
        if height2 != 0:
            run_command(f'py-ini-config set {config_path}/ccs.conf Main settings_tab_right_widget_nonzero_height2 '
                        f'{height2}')

    def expand_right_tab_text_browser(self) -> None:
        """
        По F4 сворачивает и разворачивает текстовое поле на вкладке настройки хостов
        """
        height1, height2 = self.right_panel_splitter.sizes()
        if int(height2) > 0:
            self.right_panel_splitter.setSizes([int(height1) + int(height2), 0])
        else:
            try:
                last_nonzero_height2 = int(run_command(f'py-ini-config get {config_path}/ccs.conf Main '
                                                       f'settings_tab_right_widget_nonzero_height2'))
                self.right_panel_splitter.setSizes([height1 - last_nonzero_height2, last_nonzero_height2])
            except Exception as e:
                logging.info(f'Не удалось изменить размер текстового поля: {e}')

    def keyPressEvent(self, a0: QtGui.QKeyEvent) -> None:
        if a0.key() == Qt.Key_F4:
            self.expand_right_tab_text_browser()
        a0.accept()
